/*
 * Copyright (c) 2019 TangHaifeng <tanghaifeng-gz@loongson.cn>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <asm/io.h>
#include <i2c.h>
#ifdef CONFIG_DM_I2C
#include <dm.h>
#endif

#if defined(CONFIG_MACH_LOONGSON1)
#include <mach/ls1x.h>
#elif defined(CONFIG_MACH_LOONGSON2_SOC)
#include <mach/loongson.h>
#endif

DECLARE_GLOBAL_DATA_PTR;

struct loongson_i2c_regs {
	unsigned char prerlo;
	unsigned char prerhi;
	unsigned char control;
	unsigned char data;
	unsigned char cr_sr;
};

#ifdef CONFIG_DM_I2C
struct loongson_i2c_dev {
	/* TWSI Register base for the device */
	struct loongson_i2c_regs *base;
	/* Number of the device (determined from cell-index property) */
	int index;
	/* The I2C slave address for the device */
	u8 slaveadd;
	/* The configured I2C speed in Hz */
	uint speed;
};
#endif /* CONFIG_DM_I2C */

#define OCI2C_CTRL_IEN		0x40
#define OCI2C_CTRL_EN		0x80

#define OCI2C_CMD_START		0x90
#define OCI2C_CMD_STOP		0x40
#define OCI2C_CMD_READ		0x20
#define OCI2C_CMD_WRITE		0x10
#define OCI2C_CMD_READ_ACK	0x20
#define OCI2C_CMD_READ_NACK	0x28
#define OCI2C_CMD_IACK		0x00

#define OCI2C_STAT_IF		0x01
#define OCI2C_STAT_TIP		0x02
#define OCI2C_STAT_ARBLOST	0x20
#define OCI2C_STAT_BUSY		0x40
#define OCI2C_STAT_NACK		0x80

#ifndef CONFIG_DM_I2C

/*
 * loongson_get_base() - Get controller register base for specified adapter
 *
 * @adap:	Adapter to get the register base for.
 * @return Register base for the specified adapter.
 */
static struct loongson_i2c_regs *loongson_get_base(struct i2c_adapter *adap)
{
	switch (adap->hwadapnr) {
#ifdef CONFIG_SYS_I2C_LOONGSON_CH0
	case 0:
		return (struct loongson_i2c_regs *)CONFIG_SYS_I2C_LOONGSON_CH0;
#endif
#ifdef CONFIG_SYS_I2C_LOONGSON_CH1
	case 1:
		return (struct loongson_i2c_regs *)CONFIG_SYS_I2C_LOONGSON_CH1;
#endif
	default:
		printf("Missing loongson i2c controller %d base\n", adap->hwadapnr);
		break;
	}

	return NULL;
}
#endif

/*
 * Poll the i2c status register until the specified bit is set.
 * Returns 0 if timed out (200 msec).
 */
static int loongson_poll_status(struct loongson_i2c_regs *regs, unsigned long bit)
{
	int loop_cntr = 20000;

	do {
		udelay(10);
	} while ((readb(&regs->cr_sr) & bit) && (--loop_cntr > 0));

	return (loop_cntr > 0);
}

static int loongson_xfer_read(struct loongson_i2c_regs *regs, unsigned char *buf, int length)
{
	int x;

	for (x=0; x<length; x++) {
		/* send NACK last not send ACK */
		if (x != (length -1))
			writeb(OCI2C_CMD_READ_ACK, &regs->cr_sr);
		else
			writeb(OCI2C_CMD_READ_NACK, &regs->cr_sr);

		if (!loongson_poll_status(regs, OCI2C_STAT_TIP)) {
			return -1;
		}
		*buf++ = readb(&regs->data);
	}

	return 0;
}

static int loongson_xfer_write(struct loongson_i2c_regs *regs, unsigned char *buf, int length)
{
	int x;

	for (x=0; x<length; x++) {
		writeb(*buf++, &regs->data);
		writeb(OCI2C_CMD_WRITE, &regs->cr_sr);
		if (!loongson_poll_status(regs, OCI2C_STAT_TIP)) {
			return -1;
		}
		if (readb(&regs->cr_sr) & OCI2C_STAT_NACK) {
			writeb(OCI2C_CMD_STOP, &regs->cr_sr);
			return length;
		}
	}

	return 0;
}

static int i2c_begin(struct loongson_i2c_regs *regs, u8 chip)
{
	if (!loongson_poll_status(regs, OCI2C_STAT_BUSY)) {
		printf("err: i2c adapter busy\r\n");
		goto err;
	}

	writeb(chip, &regs->data);
	writeb(OCI2C_CMD_START, &regs->cr_sr);

	/* Wait until transfer is finished */
	if (!loongson_poll_status(regs, OCI2C_STAT_TIP)) {
		printf("err: i2c adapter timeout\r\n");
		goto err;
	}

	if (readb(&regs->cr_sr) & OCI2C_STAT_NACK) {
		printf("err: i2c slave addr nack\r\n");
		goto err;
	}

	return 0;
err:
	return -1;
}

static uint __loongson_i2c_set_bus_speed(struct loongson_i2c_regs *regs,
				     uint requested_speed)
{
	unsigned long clk;
	int prescale;

	clk = gd->bus_clk;
	prescale = (clk / (5*requested_speed)) - 1;

	writeb(prescale & 0xff, &regs->prerlo);
	writeb(prescale >> 8, &regs->prerhi);

	return requested_speed;
}

static void __loongson_i2c_init(struct loongson_i2c_regs *regs, int speed,
			    int slaveadd, uint *actual_speed)
{
	uint tmp_speed;
	u8 ctrl = readb(&regs->control);

#ifdef CONFIG_CPU_LOONGSON2K500
	/* 适配器配置成主模式 */
	ctrl |= 0x20;
#endif

	/* make sure the device is disabled */
	writeb(ctrl & ~(OCI2C_CTRL_EN | OCI2C_CTRL_IEN), &regs->control);
	/* Set i2c interface clock as specified */
	tmp_speed = __loongson_i2c_set_bus_speed(regs, speed);
	if (actual_speed)
		*actual_speed = tmp_speed;

	/* Init the device */
	writeb(OCI2C_CMD_IACK, &regs->cr_sr);
	writeb(ctrl | OCI2C_CTRL_EN, &regs->control);
}

static int __loongson_i2c_probe_chip(struct loongson_i2c_regs *regs, uchar chip)
{
	int status;
	u8 byte;

	/* Begin i2c read */
	status = i2c_begin(regs, (chip << 1) | 1);
	/* Dummy read was accepted: receive byte, but NAK it. */
	if (status == 0)
		status = loongson_xfer_read(regs, &byte, 1);
	/* Stop transaction */
	writeb(OCI2C_CMD_STOP, &regs->cr_sr);
	/* Return 0, or the status of the first failure */
	return status;
}

static int __loongson_i2c_read(struct loongson_i2c_regs *regs, uchar chip,
			   u8 *addr, int alen, uchar *buffer, int len)
{
	int ret = 0;

	if (alen > 0) {
		ret = i2c_begin(regs, (chip << 1) | 0);
		if (ret)
			goto err;
		ret = loongson_xfer_write(regs, addr, alen);
		if (ret)
			goto err;
	}
	writeb(OCI2C_CMD_STOP, &regs->cr_sr);

	ret = i2c_begin(regs, (chip << 1) | 1);
	if (ret)
		goto err;

	ret = loongson_xfer_read(regs, buffer, len);
	if (ret)
		goto err;

err:
	writeb(OCI2C_CMD_STOP, &regs->cr_sr);
	return ret;
}

static int __loongson_i2c_write(struct loongson_i2c_regs *regs, uchar chip,
			    u8 *addr, int alen, uchar *buffer, int len)
{
	int ret = 0;

	ret = i2c_begin(regs, (chip << 1) | 0);
	if (ret)
		goto err;

	if (alen > 0) {
		ret = loongson_xfer_write(regs, addr, alen);
		if (ret)
			goto err;
	}

	ret = loongson_xfer_write(regs, buffer, len);
	if (ret)
		goto err;

err:
	writeb(OCI2C_CMD_STOP, &regs->cr_sr);
	return ret;
}

#ifndef CONFIG_DM_I2C
static void loongson_i2c_init(struct i2c_adapter *adap, int speed, int slaveadd)
{
	struct loongson_i2c_regs *regs = loongson_get_base(adap);
	__loongson_i2c_init(regs, speed, slaveadd, NULL);
}

static uint loongson_i2c_set_bus_speed(struct i2c_adapter *adap, uint speed)
{
	struct loongson_i2c_regs *regs = loongson_get_base(adap);

	__loongson_i2c_set_bus_speed(regs, speed);

	return speed;
}

static int loongson_i2c_probe(struct i2c_adapter *adap, uchar chip)
{
	struct loongson_i2c_regs *regs = loongson_get_base(adap);
	return __loongson_i2c_probe_chip(regs, chip);
}

static int loongson_i2c_read(struct i2c_adapter *adap, uchar chip, uint addr,
			 int alen, uchar *buffer, int len)
{
	struct loongson_i2c_regs *regs = loongson_get_base(adap);
	uchar addr_buffer[] = {
		(addr >>  0),
		(addr >>  8),
		(addr >> 16),
	};

	return __loongson_i2c_read(regs, chip, addr_buffer, alen, buffer, len);
}

static int loongson_i2c_write(struct i2c_adapter *adap, uchar chip, uint addr,
			  int alen, uchar *buffer, int len)
{
	struct loongson_i2c_regs *regs = loongson_get_base(adap);
	uchar addr_buffer[] = {
		(addr >>  0),
		(addr >>  8),
		(addr >> 16),
	};

	return __loongson_i2c_write(regs, chip, addr_buffer, alen, buffer, len);
}

/*
 * Register loongson i2c adapters
 */
#ifdef CONFIG_SYS_I2C_LOONGSON_CH0
U_BOOT_I2C_ADAP_COMPLETE(loongson_0, loongson_i2c_init, loongson_i2c_probe,
			 loongson_i2c_read, loongson_i2c_write,
			 loongson_i2c_set_bus_speed,
			 CONFIG_SYS_LOONGSON_I2C0_SPEED,
			 CONFIG_SYS_LOONGSON_I2C0_SLAVE, 0)
#endif
#ifdef CONFIG_SYS_I2C_LOONGSON_CH1
U_BOOT_I2C_ADAP_COMPLETE(loongson_1, loongson_i2c_init, loongson_i2c_probe,
			 loongson_i2c_read, loongson_i2c_write,
			 loongson_i2c_set_bus_speed,
			 CONFIG_SYS_LOONGSON_I2C1_SPEED,
			 CONFIG_SYS_LOONGSON_I2C1_SLAVE, 1)
#endif

#else /* CONFIG_DM_I2C */
static int loongson_i2c_probe_chip(struct udevice *bus, u32 chip_addr,
				 u32 chip_flags)
{
	struct loongson_i2c_dev *dev = dev_get_priv(bus);
	return __loongson_i2c_probe_chip(dev->base, chip_addr);
}

static int loongson_i2c_set_bus_speed(struct udevice *bus, uint speed)
{
	struct loongson_i2c_dev *dev = dev_get_priv(bus);

	dev->speed = __loongson_i2c_set_bus_speed(dev->base, speed);

	return 0;
}

static int loongson_i2c_ofdata_to_platdata(struct udevice *bus)
{
	struct loongson_i2c_dev *dev = dev_get_priv(bus);

	dev->base = map_physmem(devfdt_get_addr(bus),
				 sizeof(struct loongson_i2c_regs),
				 MAP_NOCACHE);

	if (!dev->base)
		return -ENOMEM;

	dev->index = fdtdec_get_int(gd->fdt_blob, dev_of_offset(bus),
				    "cell-index", -1);
	dev->slaveadd = fdtdec_get_int(gd->fdt_blob, dev_of_offset(bus),
				       "u-boot,i2c-slave-addr", 0x0);
	dev->speed = fdtdec_get_int(gd->fdt_blob, dev_of_offset(bus),
				    "clock-frequency", 0x0);
	return 0;
}

static int loongson_i2c_bind(struct udevice *bus)
{
//	struct loongson_i2c_regs *regs = devfdt_get_addr_ptr(bus);

	return 0;
}

static int loongson_i2c_probe(struct udevice *bus)
{
	struct loongson_i2c_dev *dev = dev_get_priv(bus);
	uint actual_speed;

	__loongson_i2c_init(dev->base, dev->speed, dev->slaveadd, &actual_speed);
	dev->speed = actual_speed;

	return 0;
}

static int loongson_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs)
{
	struct loongson_i2c_dev *dev = dev_get_priv(bus);
	struct i2c_msg *dmsg, *omsg, dummy;

	memset(&dummy, 0, sizeof(struct i2c_msg));

	/* We expect either two messages (one with an offset and one with the
	 * actual data) or one message (just data or offset/data combined) */
	if (nmsgs > 2 || nmsgs == 0) {
		debug("%s: Only one or two messages are supported.", __func__);
		return -1;
	}

	omsg = nmsgs == 1 ? &dummy : msg;
	dmsg = nmsgs == 1 ? msg : msg + 1;

	if (dmsg->flags & I2C_M_RD)
		return __loongson_i2c_read(dev->base, dmsg->addr, omsg->buf,
				       omsg->len, dmsg->buf, dmsg->len);
	else
		return __loongson_i2c_write(dev->base, dmsg->addr, omsg->buf,
					omsg->len, dmsg->buf, dmsg->len);
}

static const struct dm_i2c_ops loongson_i2c_ops = {
	.xfer		= loongson_i2c_xfer,
	.probe_chip	= loongson_i2c_probe_chip,
	.set_bus_speed	= loongson_i2c_set_bus_speed,
};

static const struct udevice_id loongson_i2c_ids[] = {
	{ .compatible = "loongson,ls1x-i2c", },
	{ .compatible = "loongson,ls2x-i2c", },
	{ /* sentinel */ }
};

U_BOOT_DRIVER(i2c_loongson) = {
	.name = "i2c_loongson",
	.id = UCLASS_I2C,
	.of_match = loongson_i2c_ids,
	.bind = loongson_i2c_bind,
	.probe = loongson_i2c_probe,
	.ofdata_to_platdata = loongson_i2c_ofdata_to_platdata,
	.priv_auto_alloc_size = sizeof(struct loongson_i2c_dev),
	.ops = &loongson_i2c_ops,
};
#endif /* CONFIG_DM_I2C */
